import tkinter as tk
import random

# 地图大小
w = 10
h = 10
# 迷宫地图数据
points = []
# 食物们的位置（默认只有一个食物）[Y,X]
foods = [[5, 3]]
# 食物中的能量
power = 20
# 时钟，默认1秒
timer = 1000
visited = []  # 探索过
# 机器人A：Y轴，X轴，能量，损耗，记忆{坐标值:[子路口]}，属性越多优先算法越多
robot = {'y': 0, 'x': 0, 'power': 50, 'loss': 4, 'path': {}}
# 机器人时钟
robot_timer = None
play = True

# 初始化数据
def start():
    global points
    points = []
    for yi in range(h):
        points.append([])
        for xi in range(w):
            # 1：墙，0：路
            points[yi].append(1 if random.random() > 0.8 else 0)
    show()

# 显示地图
def show():
    global canvas
    canvas.delete("all")  # 清空画布
    cell_size = 30
    for yi in range(h):
        for xi in range(w):
            x1, y1 = xi * cell_size, yi * cell_size
            x2, y2 = x1 + cell_size, y1 + cell_size
            if points[yi][xi] == 1:
                color = 'black'  # 显示墙
            else:
                color = 'white'  # 显示路
            if yi == robot['y'] and xi == robot['x']:
                color = 'blue'  # 显示机器人
            # 显示符合坐标值的食物们
            for f in foods:
                if f[0] == yi and f[1] == xi:
                    # 食物坐标部分必须为路
                    color = 'orange'
                    points[yi][xi] = 0
                    break
            canvas.create_rectangle(x1, y1, x2, y2, fill=color, tags=f"{yi}_{xi}")
            canvas.tag_bind(f"{yi}_{xi}", "<Button-1>", lambda event, y=yi, x=xi: add(y, x))

# 选择迷宫游戏中的优先算法
def robot_first(val):
    global robot_timer, play
    # 重置机器人的时钟
    if robot_timer:
        root.after_cancel(robot_timer)
    # 设置机器人的默认能量
    robot['power'] = 500
    robot['path'] = {}
    visited.clear()
    play = True
    if val == "随机":  # 随机优先
        rand(-1)
    elif val == "触觉":  # 触觉优先
        wall(-1)
    elif val == "深度":  # 深度优先
        depth(-1)
    else:  # 默认等待优先
        wait()

# 等待优先：模拟只有电源的情况
def wait():
    print('等待优先')
    # 显示机器人当前状态
    show_wait()
    # 每秒钟递归一次
    if play:
        global robot_timer
        robot_timer = root.after(timer, wait)

# 等待模块：显示机器人当前的位置与吸收的总能量
def show_wait():
    global foods, play, robot_timer
    # 返回坐标值
    x = robot['x']
    y = robot['y']
    # 计算食物的总能量
    sum_power = 0
    indexs = []
    new_foods = []
    for item in foods:
        if item[0] == y and item[1] == x:
            sum_power += power
        else:
            new_foods.append(item)
    foods = new_foods
    show()
    robot['power'] += sum_power  # 增加能量点
    robot['power'] -= robot['loss']  # 扣除损耗能量点
    # 人机对话
    msg = '主人：'
    if sum_power > 0:
        msg += f'我刚刚吸收了{sum_power}点能量！'
    len_left = robot['power'] // robot['loss']  # 剩余生命
    if len_left > 0:
        msg += f'我还能坚持{len_left}秒钟！'
    else:
        msg += '再见了！'
        # 停止计算
        if robot_timer:
            root.after_cancel(robot_timer)
        play = False
    status_label.config(text=msg)

# 随机优先：模拟电源+轮子的情况
def rand(to=-1):
    print('随机优先', to)
    # 随机确定一个方向
    if to < 0:
        to = goto()
    show_wait()
    show_move(to)
    if play:
        global robot_timer
        robot_timer = root.after(timer, lambda: rand(-1))

# 移动模块：判断机器人移动方向
def show_move(to):
    road = True  # 默认通过
    xy = new_x_y(to)
    new_x, new_y = xy[0], xy[1]
    # 如果遇到墙壁或者原地不动时不移动
    if points[new_y][new_x] == 1 or (new_x == robot['x'] and new_y == robot['y']):
        road = False  # 禁止通过
    # 移动机器人到新位置
    if road:
        robot['x'] = new_x
        robot['y'] = new_y
    return road

# 触觉优先：模拟电源+轮子+触手的情况
def wall(to=-1):
    print('触觉优先', to)
    if to < 0:
        to = goto()
    show_wait()
    # 前方是否转向
    move = show_move(to)
    if not move:
        to = goto()  # 随机掉头
    if play:
        global robot_timer
        robot_timer = root.after(timer, lambda: wall(to))

# 深度优先：模拟电源+轮子+触手+记忆的情况
def depth(to=-1):
    print('深度优先', to)
    if to < 0:
        to = goto()
    # 添加子路径：上、右、下、左
    key = f"{robot['y']}_{robot['x']}"
    add_path(key)
    show_wait()
    move = show_move(to)
    if not move:
        # 寻找从未探索过的节点
        data = robot['path']
        keys = list(data.keys())
        key_len = len(keys)
        if key_len > 0:
            key_len -= 1
        # 从后向前遍历
        for l in range(key_len, -1, -1):
            ns = data[keys[l]]
            if ns:
                # 删除最后数组元素
                next_node = ns.pop()
                if next_node[0] not in visited:
                    # 逐步退回或者直接跳转到指定位置
                    visited.append(next_node[0])
                    robot['y'] = next_node[2]
                    robot['x'] = next_node[3]
                    to = goto()
                    print('瞬移到从未探索的坐标节点')
                    break
    if play:
        global robot_timer
        robot_timer = root.after(timer, lambda: depth(to))

# 添加新的路径节点
def add_path(key):
    nodes = []
    for i in range(4):
        xy = new_x_y(i)
        new_y, new_x = xy[1], xy[0]
        if points[new_y][new_x] == 0:
            # 剔除从未探索过的重复节点名称
            node = f"{new_y}_{new_x}"
            if node not in nodes and node not in visited:
                nodes.append([node, i, new_y, new_x])
    robot['path'][key] = nodes

# 放置食物
def add(y, x):
    foods.append([y, x])
    show()

# 机器人随机移动方向
def goto(n=4):
    return random.randint(0, n - 1)

# 边界判断：每次探测步长
def new_x_y(to, len=1):
    new_x = robot['x']
    new_y = robot['y']
    # 最大边界值
    max_w = w - 1
    max_h = h - 1
    # 顺时针：上、右、下、左
    if to == 0:  # 上
        new_y = max(0, new_y - len)
    elif to == 1:  # 右
        new_x = min(max_w, new_x + len)
    elif to == 2:  # 下
        new_y = min(max_h, new_y + len)
    elif to == 3:  # 左
        new_x = max(0, new_x - len)
    # 返回最终的坐标点[X,Y]
    return [new_x, new_y]

# 创建主窗口
root = tk.Tk()
root.title("迷宫游戏")

# 创建画布
canvas = tk.Canvas(root, width=w * 30, height=h * 30)
canvas.pack()

# 创建下拉菜单
options = ["请选择算法", "等待优先", "随机优先", "触觉优先", "深度优先"]
selected_option = tk.StringVar(root)
selected_option.set(options[0])
option_menu = tk.OptionMenu(root, selected_option, *options, command=robot_first)
option_menu.pack()

# 创建状态标签
status_label = tk.Label(root, text="点击迷宫投放食物！")
status_label.pack()

# 初始化游戏
start()

# 运行主循环
root.mainloop()